#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
#[serde(tag = "request", rename_all = "snake_case")]
pub enum {{ service.proto_name }}Request {
    {%- for m in service.methods %}
    {{ m.proto_name }}({{ m.input_type }}),
    {%- endfor %}
}

#[allow(clippy::large_enum_variant)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
#[serde(tag = "response", content = "content", rename_all = "snake_case")]
pub enum {{ service.proto_name }}Response {
    {%- for m in service.methods %}
    {{ m.proto_name }}({{ m.output_type }}),
    {%- endfor %}
}

pub trait {{ service.proto_name }} {
    {%- for m in service.methods %}
      fn {{ m.name }}(
          &self,
          request: teaclave_rpc::Request<{{ m.impl_input_type }}>
      ) -> teaclave_types::TeaclaveServiceResponseResult<{{ m.impl_output_type }}>;
    {%- endfor %}

    fn dispatch(
      &self,
      request: teaclave_rpc::Request<{{ service.proto_name }}Request>
    ) -> teaclave_types::TeaclaveServiceResponseResult<{{ service.proto_name }}Response> {
         use core::convert::TryFrom;
         use std::string::ToString;
         match request.message {
             {%- for m in service.methods %}
             {{ service.proto_name }}Request::{{ m.proto_name }}(r) => {
                 let r = {{ m.impl_input_type }}::try_from(r)
                     .map_err(|_| teaclave_types::TeaclaveServiceResponseError::InternalError("internal".to_string()))?;
                 let r = teaclave_rpc::Request {
                     metadata: request.metadata,
                     message: r,
                 };
                 let response = self.{{ m.name }}(r)?;
                 let response = {{ m.output_type }}::from(response);
                 Ok(response).map({{ service.proto_name }}Response::{{ m.proto_name }})
             },
             {%- endfor %}
         }
    }
}

pub struct {{ service.proto_name }}Client {
    channel: teaclave_rpc::channel::SgxTrustedTlsChannel<{{ service.proto_name }}Request, {{ service.proto_name }}Response>,
    metadata: std::collections::HashMap<std::string::String, std::string::String>,
}

impl {{ service.proto_name }}Client {
    pub fn new(
        channel: teaclave_rpc::channel::SgxTrustedTlsChannel<
            {{ service.proto_name }}Request,
            {{ service.proto_name }}Response
        >
    ) -> anyhow::Result<Self> {
        let metadata = std::collections::HashMap::new();
        Ok(Self { channel, metadata })
    }

    pub fn new_with_metadata(
        channel: teaclave_rpc::channel::SgxTrustedTlsChannel<
            {{ service.proto_name }}Request,
            {{ service.proto_name }}Response
        >,
        metadata: std::collections::HashMap<std::string::String, std::string::String>,
    ) -> anyhow::Result<Self> {
        Ok(Self { channel, metadata })
    }

    {%- for m in service.methods %}
    pub fn {{ m.name }}<T: teaclave_rpc::IntoRequest<{{ service.proto_name }}Request>>(
        &mut self,
        request: T
    ) -> teaclave_types::TeaclaveServiceResponseResult<{{ m.impl_output_type }}> {
        use core::convert::TryInto;
        use std::string::ToString;
        {# {%- if service.methods.len() > 1 %} #}
        {# use std::string::ToString; #}
        {# {%- endif %} #}
        let mut request = request.into_request();
        request.metadata = self.metadata.clone();

        match self.channel.invoke(request) {
            Ok({{ service.proto_name }}Response::{{ m.proto_name }}(response)) => Ok(response.try_into().map_err(|_| teaclave_types::TeaclaveServiceResponseError::InternalError("internal".to_string()))?),
            Err(e) => Err(e),
            {%- if service.methods.len() > 1 %}
            _ => Err(teaclave_types::TeaclaveServiceResponseError::InternalError("internal".to_string())),
            {%- endif %}
        }
    }
    {%- endfor %}

    pub fn metadata(&self) -> &std::collections::HashMap<std::string::String, std::string::String> {
        &self.metadata
    }

    pub fn metadata_mut(&mut self) -> &mut std::collections::HashMap<std::string::String, std::string::String> {
        &mut self.metadata
    }

    pub fn set_metadata(&mut self, metadata: std::collections::HashMap<std::string::String, std::string::String>) {
        self.metadata = metadata
    }
}
